package org.hackystat.sensor.ant.checkstyle;

import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.tools.ant.BuildException;
import org.hackystat.sensor.ant.checkstyle.jaxb.Checkstyle;
import org.hackystat.sensor.ant.checkstyle.jaxb.Error;
import org.hackystat.sensor.ant.task.HackystatSensorTask;
import org.hackystat.sensor.ant.util.LongTimeConverter;
import org.hackystat.sensorshell.SensorShellException;

/**
 * Implements an Ant task that parses the XML files generated by Checkstyle. The Ant Task sends the
 * CodeIssue data to a Hackystat server.
 * 
 * @author Julie Ann Sakuda, Philip Johnson
 */
public class CheckstyleSensor extends HackystatSensorTask {
  
  /** The type prefix for all attributes representing code issue errors. */
  private static final String TYPE = "Type_";
  /** The name of this tool. */
  private static String tool = "Checkstyle";

  /** Initialize a new instance of a CheckstyleSensor. */
  public CheckstyleSensor() {
    super(tool);
  }

  /**
   * Initialize a new instance of a CheckstyleSensor for testing purposes.
   * 
   * @param host The SensorBase host URL.
   * @param email The SensorBase email to use.
   * @param password The SensorBase password to use.
   */
  public CheckstyleSensor(String host, String email, String password) {
    super(host, email, password, tool);
  }


  /**
   * Parses the Checkstyle XML files and sends the resulting code issue results to the SensorBase
   * server. 
   * 
   * @throws BuildException If there is an error.
   */
  @Override
  public void executeInternal() throws BuildException {
    setupSensorShell();

    int numberOfCodeIssues = 0;
    Date startTime = new Date();
    // Iterate though each file, extract the Checkstyle data, send to sensorshell.
    for (File dataFile : getDataFiles()) {
      verboseInfo("Processing Checkstyle file: " + dataFile);
      try {
        numberOfCodeIssues += this.processIssueXmlFile(dataFile);
      }
      catch (Exception e) {
        signalError("Failure processing: " + dataFile, e);
      }
    }
    this.sendAndQuit();
    summaryInfo(startTime, "Checkstyle", numberOfCodeIssues);
  }

  /**
   * Parses a Checkstyle XML file and sends the code issue instances to the shell.
   * 
   * @param xmlFile The XML file name to be processed.
   * @return The number of issues that have been processed in this XML file.
   * @exception BuildException thrown if it fails to process a file.
   */
  public int processIssueXmlFile(File xmlFile) throws BuildException {
    XMLGregorianCalendar runtimeGregorian = LongTimeConverter.convertLongToGregorian(this.runtime);
    try {
      JAXBContext context = JAXBContext
          .newInstance(org.hackystat.sensor.ant.checkstyle.jaxb.ObjectFactory.class);
      Unmarshaller unmarshaller = context.createUnmarshaller();

      Checkstyle checkstyle = (Checkstyle) unmarshaller.unmarshal(xmlFile);
      // list of file elements in the checkstyle result file
      List<org.hackystat.sensor.ant.checkstyle.jaxb.File> checkedFiles = checkstyle
          .getFile();
      
      int codeIssueCount = 0;
      for (org.hackystat.sensor.ant.checkstyle.jaxb.File file : checkedFiles) {
        // Fully qualified name of the file checked
        String fileName = file.getName();

        // Base unique timestamp off of the runtime (which is when it start running)
        long uniqueTstamp = this.tstampSet.getUniqueTstamp(this.runtime);

        // Get altered time as XMLGregorianCalendar
        XMLGregorianCalendar uniqueTstampGregorian = LongTimeConverter
            .convertLongToGregorian(uniqueTstamp);
        
        // Add required information to the sensor key-val map
        Map<String, String> keyValMap = new HashMap<String, String>();
        keyValMap.put("Tool", "Checkstyle");
        keyValMap.put("SensorDataType", "CodeIssue");
        keyValMap.put("Timestamp", uniqueTstampGregorian.toString());
        keyValMap.put("Runtime", runtimeGregorian.toString());
        keyValMap.put("Resource", fileName);
        
        Map<String, Integer> issueCounts = new HashMap<String, Integer>();
        
        // gets all error elements for the file
        List<Error> errors = file.getError();
        
        // file has errors, send one entry per error
        for (Error error : errors) {
          String source = error.getSource();
          
          String[] tokens = source.split("\\.");
          String rule = tokens[tokens.length - 1];
          
          if (issueCounts.containsKey(rule)) {
            Integer count = issueCounts.get(rule);
            issueCounts.put(rule, ++count);
          }
          else {
            // no mapping, first occurrence
            issueCounts.put(rule, 1);
          }
        }
        
        // Add the issue counts to the key-val map
        for (Entry<String, Integer> entry : issueCounts.entrySet()) {
          String typeKey = TYPE + entry.getKey();
          keyValMap.put(typeKey, entry.getValue().toString());
        }
        
        this.sensorShell.add(keyValMap);
        codeIssueCount++;
      }
      return codeIssueCount;
    }
    catch (JAXBException e) {
      throw new BuildException(errMsgPrefix + "Failure in JAXB " + xmlFile, e);
    }
    catch (SensorShellException f) {
      throw new BuildException(errMsgPrefix + "Failure in SensorShell " + xmlFile, f);
    }
  }
}